package protocol

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"ezcloud/gw/internal/conf"
	"ezcloud/gw/internal/define"
	"ezcloud/gw/internal/log"
	"ezcloud/gw/internal/mods/mod_header"
	"ezcloud/gw/internal/mods/mod_logid"
	"ezcloud/gw/internal/mods/mod_rewrite"
	"ezcloud/gw/internal/util"
	"ezcloud/gw/pkg/filter"
	"ezcloud/gw/pkg/route"
	"ezcloud/gw/pkg/upstream"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"time"
)

var ErrorNotFoundRoute = errors.New("not found route")
var ErrorGwInternal = errors.New("gateway internal error")

type HttpProtocol struct {
	config conf.ProtocolConfig

	server *http.Server

	routes   *route.Routes
	filters  *filter.Fillers
	upstream *upstream.Upstreams
}

func NewHttpProtocol(c conf.ProtocolConfig) Protocol {
	return &HttpProtocol{config: c}
}

func (F *HttpProtocol) Start(l net.Listener) error {
	// 加载 routes
	F.routes = route.NewRoutes()
	F.routes.LoadRoute()

	// 加载 Filter
	F.filters = filter.NewFilters()
	F.filters.LoadFilter()

	// 加载上游
	F.upstream = upstream.NewUpstreams()
	F.upstream.LoadTarget()
	F.upstream.LoadProvider()

	F.server = &http.Server{
		Handler:           F,
		ReadTimeout:       60 * time.Second,
		ReadHeaderTimeout: 30 * time.Second,
		WriteTimeout:      60 * time.Second,
		MaxHeaderBytes:    1 << 20, // 1 MB,
		ErrorLog:          log.SysLogger(),
		ConnContext: func(ctx context.Context, c net.Conn) context.Context {
			return context.WithValue(ctx, conf.TcpConnContextKey, c)
		},
	}

	// 启动 Https.Server
	if F.config.Ssl {
		log.Infof("start https protocol")

		// load CA certificate file and add it to list of client CAs
		caCertFile, err := ioutil.ReadFile("conf/ca.crt")
		if err != nil {
			log.Fatalf("error reading CA certificate: %v", err)
		}
		caCertPool := x509.NewCertPool()
		caCertPool.AppendCertsFromPEM(caCertFile)
		/*
		   	$> cat nginx.conf:
		   	           ssl_protocols TLSv1.2 TLSv1.3 ;
		   	   		   ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
		   	   		   ssl_prefer_server_ciphers on;
		               ssl_sessionn_tickets off;

		   	$> 相关关键字说明：
		   	tls 1.2 分组密码只支持AES GCM(Galois-MAC + CTR Mode)和AES CCM(CBC-MAC + CTR Mode )
		   	TLS 1.2 摘要使用 SHA-256/384
		   	MAC (Message Authentication Code) : 消息认证码, 对散列值进行加密后发送，使用密钥加密后的散列值.
		   	HMAC(Hash-MAC)：使用SHA-2之类的单向Hash函数实现
		   	CMAC(CBC-MAC): 使用AES之类的分组密码实现，并采用CBC模式将消息全部加密
		   	GMAC(Galois-MAC):使用AES之类的分组密码实现
		   	Poly1305：使用ChaCha20之类的流密码实现；

		   	AEAD是对称加密算法与MAC计算的组合，TLS 1.3 支持的AEAD算法有三种：AES-CCM、AES-GCM、ChaCha20-Poly1305。
		   	AES-CCM :		(AES-CTR + CBC-MAC)
		   	AES-GCM:		(AES-CTR + Galois-MAC)
		*/
		tlsConfig := &tls.Config{
			ClientCAs:                caCertPool,
			ClientAuth:               tls.RequireAndVerifyClientCert,
			MinVersion:               tls.VersionTLS12,
			CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
			PreferServerCipherSuites: true,
			SessionTicketsDisabled:   true,
			CipherSuites: []uint16{
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,

				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,

				tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
				tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,

				tls.TLS_AES_128_GCM_SHA256,
				tls.TLS_AES_256_GCM_SHA384,
			},
		}
		// 将多个证书，加到不同的 DNS Name 上.
		// tlsConfig.BuildNameToCertificate()

		F.server.TLSConfig = tlsConfig

		return F.server.ServeTLS(l, F.config.CertFile, F.config.KeyFile)
	} else {
		log.Infof("start http protocol")
		// 启动 Http.Server
		return F.server.Serve(l)
	}
}

func (F *HttpProtocol) Stop() error {
	return F.server.Close()
}

func (F *HttpProtocol) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	tcc, ok := request.Context().Value(conf.TcpConnContextKey).(*conf.TcpConnWrap)

	context := route.NewGwRouteContext()
	if ok {
		context.Tcc = tcc
		context.LocalAddr = tcc.LocalAddr()
		context.RemoteAdd = tcc.RemoteAddr()

		log.Debugf("[id: %s, L:/%s - R:/%s] serveHttp ", context.Tcc.Id(), context.LocalAddr.String(), context.RemoteAdd.String())
	}
	context.OrigRequest = request
	context.OrigResponse = writer

	chain := NewGwFilterChain()
	defer context.Destory()

	v := F.lookupRoute(context, chain)
	if v != nil {
		// 区分错误类型
		if errors.Is(v, ErrorNotFoundRoute) {
			F.notFoundRoute(context, chain, v)
		} else {
			F.gwInternalError(context, chain, v)
		}
		return
	}

	chain.Filter(context)
}

func (F *HttpProtocol) notFoundRoute(context *route.RouteContext, chain route.RouteFilterChain, e error) {
	w := context.OrigResponse
	h := w.Header()
	util.SetContentTypeText(h)
	util.SetKeepAlive(h, false)
	util.SetStatuCode(w, http.StatusNotFound)
	_, _ = io.WriteString(w, e.Error())
}

func (F *HttpProtocol) gwInternalError(context *route.RouteContext, chain route.RouteFilterChain, e error) {
	w := context.OrigResponse
	h := w.Header()
	util.SetContentTypeText(h)
	util.SetKeepAlive(h, false)
	util.SetStatuCode(w, http.StatusInternalServerError)
	_, _ = io.WriteString(w, ErrorGwInternal.Error())
}

func (F *HttpProtocol) lookupRoute(context *route.RouteContext, c *DefaultFilterChain) error {
	context.RequestMethod = context.OrigRequest.Method
	context.RequestUri = context.OrigRequest.RequestURI
	context.RequestHeader = make(http.Header, 16)
	context.RequestBody = context.OrigRequest.Body

	// TODO: 1. 查找 route
	curRoute := F.routes.FindRoute()
	context.Set(route.X_ROUTE, curRoute)

	// TODO: 2. 根据 route 的 filterDefines
	context.Set(route.X_FILTER_DEFINES, map[string]*define.FilterDefine{})

	// TODO: 3. 根据 filterDefines，加载对应 filter 实例
	c.addFilterFuncs(mod_logid.ModGenLogId, mod_header.ModDefaultHeader, mod_rewrite.ModRewritePath)

	return nil
}

type DefaultFilterChain struct {
	index   int
	filters []filter.Filter
}

func NewGwFilterChain() *DefaultFilterChain {
	return &DefaultFilterChain{index: 0, filters: make([]filter.Filter, 0, 16)}
}

func (c *DefaultFilterChain) addFilter(f filter.Filter) {
	c.filters = append(c.filters, f)
}

func (c *DefaultFilterChain) addFilters(fs ...filter.Filter) {
	c.filters = append(c.filters, fs...)
}

func (c *DefaultFilterChain) addFilterFunc(fc filter.RouteFilterFunc) {
	c.filters = append(c.filters, fc)
}

func (c *DefaultFilterChain) addFilterFuncs(fcs ...filter.RouteFilterFunc) {
	for i := 0; i < len(fcs); i++ {
		c.filters = append(c.filters, fcs[i])
	}
}

func (c *DefaultFilterChain) Filter(context *route.RouteContext) {
	if len(c.filters) > 0 && c.index < len(c.filters) {
		f := c.filters[c.index]
		c.index++
		f.Filter(context, c)
	}
}
